
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cmsis_os2.h"                          // CMSIS RTOS header file
 
/*----------------------------------------------------------------------------
 *      Thread 1 'Thread_Name': Sample thread
 
 不在Thread_Disp()驱动DAC，因为显示频率低。更新DAC需要和ADC同步（同频率）。
 *---------------------------------------------------------------------------*/
#include "dbgUart/dbgUart.h" 
#include "bsp.h"
#include "key.h"
#include "menu_seg.h"
#include "threads.h"
#include "parameter.h"
#include "preset_point.h"
#include "drv_ch423s.h"
#include "Thread_disp.h"
#include "weight_polyline.h"


#define SLEEP_TMO		(5*60*1000)		//当SLEEP_TMO时间内无按键，进入熄屏模式。


extern uint8_t in_sta;							//IN1状态：1---有效（valide），0---无效（invalid）。
extern uint8_t flag_type;
extern uint8_t flag_para_ok;
extern int32_t para_to_flash_2(uint32_t );
extern uint16_t CRC16(uint8_t *puchMsg, uint16_t usDataLen);
extern uint8_t flag_para_update_fir;






PRESET_P preset_point[5];	//[0]不使用，使用[1~4]。

//static int32_t norm_in1_deal(MSG_U msg, int32_t wt);
static void seg_pwr_on_disp(void);
static void do_ser_cal(MSG_U* msg);

static int32_t prepoint_satisfy(uint32_t n, int32_t val);
static void prepoint_sta(uint32_t n, int32_t val);

static void spoint_config(void);
static void zz_det_init(void);



#ifdef OFFSET_DIV_N_F
float offset_div_n_f_man;				//手动触发
float offset_div_n_f_pwr_on;			//上电自动清零
float offset_div_n_f_trace;				//零点跟踪
float offset_div_n_f_tare;				//去皮
#else
int32_t offset_man;			//手动触发				在显示模式下，按下zero键；显示值在清零范围。			-1*显示值 --> offset
int32_t	offset_pwr_on;		//上电自动清零			上电；上电清零开关有效；显示值在清零范围。				-1*显示值 --> offset_pwr_on
int32_t	offset_trace;		//零点跟踪				显示值在跟踪范围；持续时间超过1.6秒。					-1*显示值 --> offset_zero_range
#endif

SLEEP sleep;							//成员变量默认为0。
uint8_t flag_clear_sleep_off;			//是否清零SLEEP结构体。
uint8_t flag_NOT_in_menu_mode;			//是否在MENU_MODE，是为1，不是为0。在MENU_MODE时，不实时处理OUT1，OUT2。
uint8_t flag_disp_norm;					//是否完成上电显示Logo等操作。

int32_t wt_mqtt;						//用来mqtt的数据。


typedef enum{
	DISP_NORM, 
	DISP_ERR, 
	DISP_MENU, 
	DISP_ANA, 
	DISP_HOLD,
	DISP_HOLD_WT,		//准备作废。
	DISP_CAL,			//标定模式（显示mv值）。因为只有在Thread_Disp()中能获得mV和称重值。
	DISP_SLEEP,			//省电模式：数码管显示"-     "。
}DISP_MODE;				//0-based. 数码管的显示模式（简称显示模式，区别于单个seg的显示模式：SEG_MODE）。

DISP_MODE disp_mode= DISP_NORM;

LAJITONG_DET lajitong_det={.sta= STA_LAJITONG_IDLE, .ts_in1_pre= -1, .to_mode= LAJITONG_SHOW_0};


/*菜单项目
*/ 
void Thread_Disp (void *argument);                   // thread function
 

int Init_Thread_Disp (void) {
	osThreadId_t tid_Thread;                        // thread id
	
	const osThreadAttr_t attr={.stack_size= 2048};                       // thread id 
	tid_Thread = osThreadNew(Thread_Disp, NULL, &attr);
	if (tid_Thread == NULL) {
		return(-1);
	}
 
	return(0);
}


//extern int32_t offset_disp;		//显示时的偏移量。



__NO_RETURN void Thread_Disp (void *argument) {
	(void)argument;	//Prevent unused argument(s) compilation warning.
	
	char str[128];
	char str_ts[20];	//含'\0'的个数。
	char str_wt[20];		//含'\0'的个数和可能的1小数点，例："12.3"，最长"12345.6"。
	char str_gps[256];	//含'\0'的个数。
	uint8_t sta_cal;
	uint32_t ts_stab;		//稳定开始时间戳
	uint32_t ts_cal_s;		//标定过程开始时间戳。
	
	int32_t wt;
	int32_t wt_div_n;
	osStatus_t ret;
	uint16_t key;				//从消息队列中获取的按键值。
	MSG_U msg;					//组合消息
	ZZONE_DET zz_det;
	
	
	zz_det.cnt_ok= 0;
	zz_det.cnt_ko= 0;
	zz_det.ts= 0;
	zz_det.sta_wt_pre= 0;
	zz_det.sta_wt= 0;
	zz_det.sta_rst_pre= STA_RST_ZZ;
	zz_det.sta_rst= STA_RST_ZZ;			//0---非复位状态，其他---复位状态。

	sta_cal= 0;
	
	spoint_config();
	
	menu_init();
	//
	//bsp_seg_hal();			//使用CH423S，不再直接驱动数码管。
	while(0 == flag_para_ok){
		osDelay(100);
	}
	
	uartS("\nThread_disp(N) start...\n");
	
	
	seg_pwr_on_disp();			//上电启动界面。
	
	BLE_ON;						//!!开启BLE芯片供电，造成3.3V瞬间跌落，可能引起MCU进入HardFault。
	//BLE_OFF;
	
	disp_mode= DISP_NORM;
	flag_NOT_in_menu_mode= 1;
	flag_disp_norm= 1;
	lajitong_det.sta= STA_LAJITONG_WAIT_UP;
	
	
	while (1) {
		//接收按键消息、INx消息或称重消息。
		ret= osMessageQueueGet(mid_key_disp_io, &msg, 0, 10);
		
		if(osOK != ret){
			//无消息时的超时处理。!!不能去掉。
			continue;			
		}
		
		
		if(MSG_TYPE_MV_WT == msg.type){
			wt= msg.wt;					//更新称重值，以便后用。
			
			osKernelLock();
			wt_mqtt= (int32_t)msg.wt_f;
			osKernelUnlock();
			
			
			if(dbg_sw & DBG_THREAD_DISP)			
				uartS("msg_mv_wt");
			
			/*限制点参与称重处理（未放在ADC ISR，因考虑不需要实时）。
			和Thread_In()中可能的重置限制点状态要互斥。
			*/
			osMutexAcquire(mux_id_prepoint, 1000);	
			prepoint_sta(1, msg.wt);
			prepoint_sta(2, msg.wt);
			prepoint_sta(3, msg.wt);
			prepoint_sta(4, msg.wt);
			osMutexRelease(mux_id_prepoint);		
			
			
			
//			/*垃圾车应用时的零区处理：由称重值驱动，无称重值，不执行。
//			在指定时长，称重值连续在零区值内，则垃圾车检测时序复位。
//			*/
//			if(para_self.laji_zzone_tmo){	//零区有效，才会判断。
//				zz_det_update(&zz_det, wt);
//			}
//			else{
//				//未使用零区，则认为处于STA_RST_LOAD。
//				zz_det.sta_rst= STA_RST_LOAD;
//				zz_det.sta_rst_pre= STA_RST_LOAD;
//			}

//			//零区值进行参与（仅垃圾车有效）：!!当零区有效，强制倾倒过程状态处于_LAJI_IDLE。
//			if(STA_RST_ZZ == zz_det.sta_rst){
//				laji_det.sta= STA_LAJITONG_IDLE;
//				
//				if(dbg_sw & DBG_LAJITONG)
//					uartS("\nLAJI:-->LAJI_IDLE");
//			}
//			else{
//				if(STA_LAJITONG_IDLE == laji_det.sta){
//					laji_det.sta= STA_LAJITONG_WAIT_UP;
//					
//					if(dbg_sw & DBG_LAJITONG)
//						uartS("\nLAJI:IDLE-->WAIT_UP");
//				}
//			}
		}
		else{
			if(dbg_sw & DBG_THREAD_DISP)
				uartS("msg_key");		//线程Thread_In()定时发送In1状态，In1也按msg_key处理。
			
			//处理重启虚拟按键。
			if(VKEY_RST == msg.key){
				dbg_flag.rst= 1;
				dbg_flag.p1= 88;
			}
			
			//总是判断按键是否空闲。熄屏
			if( (KEY_K1_ESC   ==  msg.key) ||
				(KEY_K2_OPT   ==  msg.key) ||
				(KEY_K3_MODE  ==  msg.key) ||
				(KEY_K4_ENTER ==  msg.key) ){
				
				sleep.ts_s= ts_get();	//按键开始也就是无按键。
				if(sleep.sleep_off){
					sleep.sleep_off= 0;
					continue;		//忽略这个唤醒按键功能。
				}
			}
		}
		
		//是否清零SLEEP结构体，因为菜单项变化。熄屏
		if(flag_clear_sleep_off){
			flag_clear_sleep_off= 0;
			sleep.ts_s= ts_get();
			sleep.sleep_off= 0;
		}
		
		
		/*串口标定处理：不管处于什么显示状态，都要进入标定状态（机）。
		!!标定过程需要uV，AD值，只有在本线程才能得到。
		*/
		if(1 == op_ser_cal.is_sw_cal){
			uartS("\n do_ser_cal in DISP_CAL");
		
			disp_mode= DISP_CAL;
			op_ser_cal.is_sw_cal=2;		//修改为2，以不在进入这里重复输出"\n do_ser_cal in DISP_CAL"。
		}
		
		
		if(DISP_CAL == disp_mode){	//标定模式（显示mv值）
			if(MSG_TYPE_MV_WT == msg.type){
				//只处理称重值消息（不处理按键等...）
				switch(sta_cal){
					case 0:		//设置滤波器
						//准备调整校准过程中的滤波强度。
						flag_para_update_fir= 'C';
						ts_stab= ts_get();	//假定初始稳定。
						ts_cal_s= ts_stab;	//标定开始时间
					
						sta_cal= 1;
						
						//标定时，复位所有不补偿值。
						offset_div_n_f_man= 0.0F;
						offset_div_n_f_pwr_on= 0.0F;
						offset_div_n_f_trace= 0.0F;
						offset_div_n_f_tare= 0.0F;
					
						uartS("\ndo_ser_cal: reset all offset"); 
					
						break;
					
					case 1:		//等待稳定
						//显示mv值。
						sprintf(str, "%06d", msg.uv);
						if(0 == stab_arr.mv_stab)
							x_disp_now(str, 0, LED_STAB_BIT);
						else
							x_disp_now(str, LED_STAB_BIT, LED_STAB_BIT);
						
						x_seg_set_point(SEG_L_3, 1);
				
						
						if(stab_arr.mv_stab){
							if( 1000<= ts_del_o_n(ts_stab, ts_get()) ){
								//连续稳定1秒
								sta_cal= 2;
							}
						}
						else{
							ts_stab= ts_get();	//不稳就更新时间戳
							
							if( 6000< ts_del_o_n(ts_cal_s, ts_get()) ){
								//标定过程超时
								
								sta_cal= 3;
							}
						}
							
						break;
					
					case 2:		//标定，并退出（重设滤波器）
						do_ser_cal(&msg);	//!!函数内没有延时、超时等操作，是一锤子操作。
						
						flag_para_update_fir= 'X';
					
						disp_mode= DISP_NORM;	//返回正常显示模式
						op_ser_cal.is_sw_cal= 0;
						sta_cal= 0;
					
						break;
					
					case 3:	//标定超时，不响应？
						uartS("\n$err:tmo in cal");
						disp_mode= DISP_NORM;	//返回正常显示模式 
						op_ser_cal.is_sw_cal= 0;
						op_ser_cal.op_sta= -1;
						op_ser_cal.op_ret= -3;	//标定过程超时，根因是标定中不稳定。
						sta_cal= 0;
					
						break;
					default:
						break;
				}
			}
		}			
		else if(DISP_NORM == disp_mode){			//称重模式（正常显示模式）
			
			/*查看io def中IN1的选择，若非“无定义”，则需要处理：
			QQ仅仅在DISP_NORM中执行，对吗？
			*/
			if(MSG_TYPE_KEY == msg.type){
				//处理IN1的消息
				norm_in1_deal(msg, wt);			//wt是上一次称重消息中的重量，非实时称重值。!!目前只在DISP_NORM下进行检测。
			}	
			
			
			/*更新显示：由称重值驱动。
			*/
			if(MSG_TYPE_MV_WT == msg.type){
				//显示重量值
				if(SLEEP_TMO< ts_del_o_n(sleep.ts_s, ts_get())){
					sleep.ts_s= ts_get();
					sleep.sleep_off++;
					
					if(6< sleep.sleep_off)
						sleep.sleep_off= 6;	//限制最大为6.
					
					sprintf(str,"\nsleep_off=%d", sleep.sleep_off);
					uartS(str);
				}
				
				if(0 != para_xxx.disp_sleep_idx){	//熄屏使能
					if(1 == sleep.sleep_off){
						disp_mode= DISP_SLEEP;
						x_disp_now("-     ", 0, LED_STAB_BIT | LED_ZERO_BIT | LED_HOLD_BIT);
						
						uartS("\nDISP_NORM -> DISP_SLEEP");
					}
				}
				
				
				/*垃圾桶重量值入缓冲区。保存后，.sample_idx指向刚刚存入的值。
				*/
				if(0 == strcmp(ITEM_IN_8, _tbl_in[para_xxx.in1_define_idx])){
					
					lajitong_det.sample_idx++;
					if( LAJITONG_SAMPLE_CNT_MAX<= lajitong_det.sample_idx)
						lajitong_det.sample_idx= 0;	
					
					lajitong_det.sample_wt[lajitong_det.sample_idx]= msg.wt_f;
				}
				
				
				
				if(LAJITONG_SHOW_0 == lajitong_det.to_mode){			//正常显示（实时）。
					seg_wt_f_to_seg(msg.wt_f, msg.wt_div_n, msg.dat[0]);
					//seg_wt_to_seg(msg.wt, msg.dat[0]);	//msg.dat[0]为指示灯位段或。
				}
				else if(LAJITONG_SHOW_UP == lajitong_det.to_mode){	//保持一段时间，显示上升采集称重值。
					//先实时显示。
					seg_wt_f_to_seg_prefix(msg.wt_f, msg.wt_div_n, msg.dat[0], 'U');
					
					
					//再判断是否达到指定的平均范围：
					if((50*_tbl_int_lajitong_sample_cnt[para_self.lajitong_sample_cnt_idx])<=  ts_del_o_n(lajitong_det.ts_up, ts_get())){
						//计算平均范围内的平均值。
						lajitong_det.wt_up= compute_avg(&lajitong_det);
						
						lajitong_det.to_mode= LAJITONG_SHOW_UP_AVG;
						lajitong_det.ts_up= ts_get();	
							
						if(dbg_sw & DBG_LAJITONG)
							uartS("\nSHOW_UP->SHOW_UP_AVG");
					}
				}
				else if(LAJITONG_SHOW_UP_AVG == lajitong_det.to_mode){	//保持显示上升阶段获取的称重值。
					//保持时间可配置。
					seg_wt_f_to_seg_prefix(lajitong_det.wt_up, 0, 0, 'u');	//seg_wt_to_seg(wt_det.wt_up, 0);
					x_seg_flash_all(0);                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
					
					if((1000*_tbl_int_disp_hold_time[para_self.lajitong_disp_hold_time_idx])< ts_del_o_n(lajitong_det.ts_up, ts_get())){
						//保持显示时间到，退回动态
						lajitong_det.to_mode= LAJITONG_SHOW_0;
						
						if(dbg_sw & DBG_LAJITONG)
							uartS("\nSHOW_UP_AVG->SHOW_0");
					}						
				}
				else if(LAJITONG_SHOW_DN == lajitong_det.to_mode){	//保持一段时间，显示下降采集称重值。
					//先实时显示。
					seg_wt_f_to_seg_prefix(msg.wt_f, msg.wt_div_n, msg.dat[0], 'N');
					
					
					//再判断是否达到指定的平均范围。
					if((50*_tbl_int_lajitong_sample_cnt[para_self.lajitong_sample_cnt_idx])<=  ts_del_o_n(lajitong_det.ts_dn, ts_get())){
						//计算平均范围内的平均值。
						lajitong_det.wt_dn= compute_avg(&lajitong_det);
						
						lajitong_det.to_mode= LAJITONG_SHOW_DN_AVG;	
						lajitong_det.ts_dn= ts_get();
						
						if(dbg_sw & DBG_LAJITONG)
							uartS("\nSHOW_DN->SHOW_DN_AVG");
					}
				}
				else if(LAJITONG_SHOW_DN_AVG == lajitong_det.to_mode){	//保持显示上升阶段获取的称重值。
					//保持时间可配置。
					seg_wt_f_to_seg_prefix(lajitong_det.wt_dn, 0, 0, 'n');	//seg_wt_to_seg(wt_det.wt_up, 0);
					x_seg_flash_all(0);       
					
										
					if((1000*_tbl_int_disp_hold_time[para_self.lajitong_disp_hold_time_idx])< ts_del_o_n(lajitong_det.ts_dn, ts_get())){
						//保持显示时间到，退回动态
						lajitong_det.to_mode= LAJITONG_SHOW_NW;	//跳到显示垃圾称重值。
						
						//在_SHOW_NW下显示的值和闪。
						lajitong_det.wt_net= lajitong_det.wt_up- lajitong_det.wt_dn;
						
						seg_wt_to_seg(lajitong_det.wt_net, 0, 0);
						//x_seg_set_one_content(SEG_L_1, 'N', 0);
						x_seg_flash_all(2); 
						
						if(dbg_sw & DBG_LAJITONG)
							uartS("\nSHOW_DN_AVG->SHOW_NET");
						
						
						/*更新三重寄存器，和状态寄存器。
						*/
						para_xxx.wt_gross= (uint16_t)lajitong_det.wt_up;
						para_xxx.wt_tare= (uint16_t)lajitong_det.wt_dn;
						para_xxx.wt_net= lajitong_det.wt_net;
						
						para_xxx.status|= BIT_MASK_LAJITONGOK;						
					}						
				}
				else if(LAJITONG_SHOW_NW == lajitong_det.to_mode){	//快闪，显示垃圾净重值。
					//seg_wt_to_seg(wt_det.wt_net, 0);
					//x_seg_set_one_content(SEG_L_1, 'N', 0);
					//x_seg_flash_all(2);	//放在这里不行，因为会反复执行。放在发生转换时：-->LAJITONG_SHOW_NW。
					
					if((2*1000*_tbl_int_disp_hold_time[para_self.lajitong_disp_hold_time_idx])< ts_del_o_n(lajitong_det.ts_dn, ts_get())){
						//保持显示时间到，退回动态
						lajitong_det.to_mode= LAJITONG_SHOW_0;	//跳到显示实时称重值。
						x_seg_flash_all(0);
						
						if(dbg_sw & DBG_LAJITONG)
							uartS("\nSHOW_NET->SHOW_0");
					}									
				}
				else{
					uartS("\n$err:LAJI_SHOW_X");
				}
				
				//wt= msg.wt;							//更新称重值，以便后用。已在上面保存更新了。
				
				if(dbg_sw & DBG_THREAD_DISP){
					sprintf(str, "\n%d:raw= %d, ad= %d, uv=%d, div_n_f= %f, wt= %d,st=%02x",TIM2->CNT, msg.raw, msg.ad, msg.uv, msg.wt_div_n_f, (int32_t)msg.wt_f, msg.dat[0]);
					uartS(str);
						
					sprintf(str, "\no_manu= %f, o_pwr=%f, o_auto=%f",offset_div_n_f_man, offset_div_n_f_pwr_on, offset_div_n_f_trace);
					uartS(str);
				}
			}
			else if(MSG_TYPE_KEY == msg.type){
				/*称重模式下的顶层菜单按键处理：
				K1_ESC:		
				K2_OPT:		
				K3_MOD:
				K4_ENTER:
				*/
				key= msg.key;
				
				if(KEY_K3_MODE == key){
					//获取第1个菜单项，并显示。
					cur_item= get_first_item();
					if(cur_item->f_enter != NULL)
						cur_item->f_enter(msg);
					
					seg_updata(cur_item->content);
					disp_mode= DISP_MENU;
					
					uartS("\nenter menu");
				}
				else if(KEY_K1_ESC == key){		//短按清零。
					if(0 == strcmp(ITEM_OFF ,_tbl_off_on[para_xxx.long_zero_idx])){
						//短按清零配置。
						if(0 == para_xxx.range_zero){
							seg_updata("Error2");
							osDelay(500);
						}
						else{
							flag_zero_man= 1;
							uartS("\n key to zero");
						}
					}
				}
				else if(KEY_K1_ESC_L == key){	//长按清零。
					if(0 == strcmp(ITEM_ON ,_tbl_off_on[para_xxx.long_zero_idx])){
						//长按清零配置。
						if(0 == para_xxx.range_zero){
							seg_updata("Error2");
							osDelay(500);
						}
						else{
							flag_zero_man= 1;
							uartS("\n key to zero");
						}
					}
				}
				else if(KEY_K2_OPT == key){
					//HOLD模式触发。
					if(0 !=strcmp(ITEM_DIG_0, _tbl_dig_0_4[para_xxx.hold_mode_idx])){
						disp_mode= DISP_HOLD;
						hold_s.hold_val= 0;
						hold_s.hold_val_div_n= 0;
						
						uartS("\nDISP_NORM-->DISP_HOLD");
					}
				}
				else if(KEY_K2_OPT_L == key){
					//同DT45：长按OPT键3秒，进入模拟量显示。
					disp_mode= DISP_ANA;
					seg_set_norm_to_ana();
					
					uartS("\nDISP_NORM-->DISP_ANA");
				}
				else if(KEY_K4_ENTER == key){
					if(0 == strcmp(ITEM_IN_6, _tbl_in[para_xxx.in1_define_idx])){	//切换保持功能下的触发状态。
						hold_s.hold_trig_sta= !hold_s.hold_trig_sta;	
						
						sprintf(str, "\nL%d,hold_trig_sta= %d", __LINE__, hold_s.hold_trig_sta);
						uartS(str);
					}
				}
				else{}
			}	
		}
		else if(DISP_HOLD_WT == disp_mode){		//称重值保持一定时长。
			
			seg_wt_to_seg(lajitong_det.wt_hold, 0, LED_HOLD_BIT);		//保持显示，HOLD灯亮）
			
		}
		else if(DISP_SLEEP == disp_mode){	//熄屏模式（此模式下不能进行装卸垃圾应用）

			if(MSG_TYPE_MV_WT == msg.type){
				x_disp_now("-     ", 0, LED_STAB_BIT | LED_ZERO_BIT | LED_HOLD_BIT);
			}
			
			if(SLEEP_TMO< ts_del_o_n(sleep.ts_s, ts_get())){
				sleep.ts_s= ts_get();
				sleep.sleep_off++;
				
				if(6< sleep.sleep_off)
					sleep.sleep_off= 6;	//限制最大为6.
			
				sprintf(str,"\nsleep_off=%d", sleep.sleep_off);
				uartS(str);
			}
			
			if(0 == sleep.sleep_off){
				//退出熄屏模式，返回正常显示模式。引起.sleep_off为0的原因：“称重值> 20d” 或 “有按键”。
				disp_mode= DISP_NORM;	
				
				uartS("\nDISP_SLEEP --> DISP_NORM");
			}
		}
		else if(DISP_HOLD == disp_mode){	//保持模式：显示值不是由msg得来。
			wt= hold_s.hold_val;
			wt_div_n= hold_s.hold_val_div_n;
			
			osKernelLock();
			para_xxx.triger_hold_hi= ((uint32_t)wt)>> 16;	//更新HOLD保持值，需锁定保护，以免只更新16bit，而非32bit。
			para_xxx.triger_hold_lo= (uint32_t)wt;
			osKernelUnlock();
			
			seg_wt_to_seg(wt, wt_div_n, LED_HOLD_BIT);		//msg.dat[0]固定为0：Hold灯亮（稳定信号，归零信号等）。

			//同时处理按键。
			if(MSG_TYPE_KEY == msg.type){
				/*仅K2_OPT有效:		
				*/
				key= msg.key;
				
				if(KEY_K2_OPT == key){
					disp_mode= DISP_NORM;

					uartS("\nDISP_HOLD-->DISP_NORM");
				}
//				else if(KEY_K4_ENTER == key){
//					if(0 == strcmp(ITEM_IN_6, _tbl_in[para_xxx.in1_define_idx])){	//切换保持功能下的触发状态。
//						hold_trig_sta= !hold_trig_sta;	
//						
//						sprintf(str, "\nL%d,hold_trig_sta= %d", __LINE__, hold_trig_sta);
//						uartS(str);
//					}
//				}
			}
		}
		else if(DISP_ANA == disp_mode){		//模拟量显示模式（和DISP_NORM一样，只是显示值不同而已）
			/*显示模拟输出：按当前的模拟输出方式显示，如当前模拟输出方式为0~20mA，则显示xx.yyy(单位是mA)。若输出方式为0~5V，则显示x.yyy(单位V）。
			按显示的更新速度（1刷新/50ms），
			短按ZERO清零，短按OPT退回DISP_NORM。
			*/
			
			/*查看io def中IN1的选择，若非“无定义”，则需要处理：
			*/
			if(MSG_TYPE_KEY == msg.type){
				//处理IN1的消息
				norm_in1_deal(msg, wt);
			}
			
			/*更新显示。
			*/
			if(MSG_TYPE_MV_WT == msg.type){				
				seg_wt_to_seg_in_ana(msg.wt, msg.wt_div_n, msg.dat[0]);	//msg.dat[0]有稳定信号，归零信号等。
				
				if(dbg_sw & DBG_THREAD_DISP){
					sprintf(str, "\nANA:%d:ad= %d, wt= %d,uv=%d,st=%02x",TIM2->CNT, msg.ad, msg.wt, msg.uv, msg.dat[0]);
					uartS(str);
						
					sprintf(str, "\no_manu= %f, o_pwr=%f, o_auto=%f",offset_div_n_f_man, offset_div_n_f_pwr_on, offset_div_n_f_trace);
					uartS(str);
				}
			}
			else if(MSG_TYPE_KEY == msg.type){
				/*模拟量显示下的按键处理：
				K1_ESC:		
				K2_OPT:		
				*/
				key= msg.key;
				
				if(KEY_K1_ESC == key){	
					if(0 == strcmp(ITEM_OFF ,_tbl_off_on[para_xxx.long_zero_idx])){	//短按清零配置。
						flag_zero_man= 1;
						uartS("\n key to zero in DISP_ANA");
					}
				}
				else if(KEY_K1_ESC_L == key){
					if(0 == strcmp(ITEM_OFF ,_tbl_off_on[para_xxx.long_zero_idx])){	//长按清零配置。
						flag_zero_man= 1;
						uartS("\n key to zero in DISP_ANA");
					}
				}				
				else if(KEY_K2_OPT == key){
					disp_mode= DISP_NORM;
					seg_set_ana_to_norm();
					
					//flag_to_zero= 1;
					uartS("\nDISP_ANA --> DISP_NORM");
				}
				else{}
			}			
		}
		else if(DISP_MENU == disp_mode){	//菜单模式
			
			flag_NOT_in_menu_mode= 0;
			
			if(MSG_TYPE_KEY == msg.type){
				key= msg.key;
				
				if((NULL == cur_item->parent) && (KEY_K1_ESC == key)){	//cur_item指向setup...cal，有ESC按键，则退出DISP_MENU。只有setup...菜单项的parent是NULL，即它们是顶层菜单。
					disp_mode= DISP_NORM;
					mode_mv_wt= MODE_WT;		//Thread_ADC()发送称重值（非uV值）。
					
					flag_NOT_in_menu_mode= 1;
					
					uartS("\nMENU to NORM");
				}
				else{
					if(cur_item->f_key != NULL)
						cur_item->f_key(msg);	//!!每个菜单项有自己的按键处理函数，主要是菜单项指针的更改。
					else{
						uartS("\n$err:no f_key");
					}
				}
			}
			else{
				//菜单项处理非按键消息（称重消息，mv消息等）。
				if(cur_item->f_key != NULL)
					cur_item->f_key(msg);		//!!每个菜单项有自己的按键处理函数，可能还要处理非按键消息（例，称重值...）。
			}
		}
		else if(DISP_ERR == disp_mode){		//错误显示模式（准备作废）
			//错误显示模式
		}
	}
}






///*【准备作废】根据输入（显示值，IN1状体），更改预置点状态。
//返回值：0
//输入参数 wt: 待显示的称重值
//*/
//static int32_t preset_point_deal(int32_t wt)
//{
//	if(( para_xxx.out1_define< 3 ) && (para_xxx.out2_define< 3)){
//		//不使用限制1~4，则直接退出。
//		return 0;
//	}
//	
//	
//	//处理OUT1
//	if(0 == strcmp(ITEM_DIG_03, _tbl_out[para_xxx.out1_define_idx])){
//		//使用限制1	
//	}
//	else if(0 == strcmp(ITEM_DIG_04, _tbl_out[para_xxx.out1_define_idx])){
//		//使用限制2
//	}
//	else if(0 == strcmp(ITEM_DIG_05, _tbl_out[para_xxx.out1_define_idx])){
//		//使用限制3
//	}
//	else if(0 == strcmp(ITEM_DIG_06, _tbl_out[para_xxx.out1_define_idx])){
//		//使用限制4
//	}
//	else
//		uartS("\nunkown out1_define");
//	
//	
//	//处理OUT2
//	if(0 == strcmp(ITEM_DIG_03, _tbl_out[para_xxx.out2_define_idx])){
//		//使用限制1
//		if(preset_point[1].flag_wait_unsatisfy){
//		
//		}
//			
//	}
//	else if(0 == strcmp(ITEM_DIG_04, _tbl_out[para_xxx.out2_define_idx])){
//		//使用限制2
//	}
//	else if(0 == strcmp(ITEM_DIG_05, _tbl_out[para_xxx.out2_define_idx])){
//		//使用限制3
//	}
//	else if(0 == strcmp(ITEM_DIG_06, _tbl_out[para_xxx.out2_define_idx])){
//		//使用限制4
//	}	
//	else
//		uartS("\nunkown out2_define");
//	
//	return 0;

//}



/*将wt输入后，看是否满足指定预置点的条件。
返回值：0---满足，其他---不满足
输入参数 wt: 称重值（待显示的）
输入参数 idx: 预置点序号，有效范围[1,4]。
!!暂不支持外部触发。
*/
static int32_t preset_point_satisfy(int32_t wt, int32_t idx)
{
	if((0 == para_xxx.preset_p1_condition) && (1 == idx)) 
		return -1;	//禁止（当前不满足）
	
	if((0 == para_xxx.preset_p2_condition) && (2 == idx)) 
		return -1;	//禁止（当前不满足）
	
	if((0 == para_xxx.preset_p3_condition) && (3 == idx)) 
		return -1;	//禁止（当前不满足）
	
	if((0 == para_xxx.preset_p4_condition) && (4 == idx)) 
		return -1;	//禁止（当前不满足）
	
	if(1 == idx){
		if(1 == para_xxx.preset_p1_condition){
			//< 
		}
		else if(2 == para_xxx.preset_p1_condition){
			//<= 
		}
		else if(3 == para_xxx.preset_p1_condition){
			//== 
		}
		else if(4 == para_xxx.preset_p1_condition){
			//>= 
		}
		else if(5 == para_xxx.preset_p1_condition){
			//> 
		}
		else if(6 == para_xxx.preset_p1_condition){
			//!= 
		}
		else if(7 == para_xxx.preset_p1_condition){
			//区间外
		}
		else if(8 == para_xxx.preset_p1_condition){
			//区间内（含区间值）
		}
	}
	
	return 0;
}



/*上电显示秒闪2次"8.8.8.8.8.8."，再显示"0ForcE"1秒。
*/
static void seg_pwr_on_disp(void)
{
	osStatus_t ret;
	uint16_t key;				//从消息队列中获取的按键值。
	MSG_U msg;					//组合消息
	uint32_t cnt= 0;
	uint32_t cp;
	int32_t icp;
	uint32_t ts,ts_2;
	char str[64];
	
//	cp= para_xxx.fs_max_hi;
//	cp<<= 16;
//	cp|= para_xxx.fs_max_lo;
//	
//	icp= (int32_t)cp;	//最大量程
	
	
	dbg_sw|= DBG_BUZ;	
	buz_beep(100);
	
//	for(int i= 0; i< 3; i++){
//		
//		seg_disp_8_dot();
//		osDelay(250);
//		
//		seg_disp_blank();
//		osDelay(250);
//	}
//	
//	buz_beep(100);
//	seg_disp_logo();
//	osDelay(1000);
	
	//dbg_sw&= ~DBG_BUZ;	//禁止蜂鸣。
	
	
	
	//上电自动清零
	if(0 == strcmp(ITEM_OFF, _tbl_off_on[para_xxx.zero_auto_sw_idx])){
		uartS("\n zero_auto NO");
		return;
	}
	
	//延时2s，以让滤波稳定。
	ts= ts_get();
	while(1){
		ret= osMessageQueueGet(mid_key_disp_io, &msg, 0, 10);
		
		ts_2= ts_get();
		if(2000< ts_del_o_n(ts, ts_2))
			break;
	}
	
	
	ts= ts_get();
	while(1){
		ret= osMessageQueueGet(mid_key_disp_io, &msg, 0, 10);
		
		if(osOK != ret){
			continue;			
		}
		
		if(MSG_TYPE_MV_WT != msg.type){
			continue;
		}	
		
		
		if(dbg_sw & DBG_THREAD_DISP)			
			uartS("\nmsg_mv_wt_pwr_on");
		
		if(msg.dat[0] & LED_STAB_BIT){
			if(msg.wt_div_n<= (cal_ctx.div_n_cap*para_xxx.range_zero/100))
				cnt++;
			else
				cnt= 0;
		} 
		else
			cnt= 0;

		
		/*N秒超时，暂定为2秒。
		*/
		ts_2= ts_get();
		sprintf(str, "\nwt= %d, n=%d, dat0=%02x",msg.wt_div_n, cnt, msg.dat[0]);
		uartS(str);

		
		if(2000< ts_del_o_n(ts, ts_2)){
			if(10<= cnt){	//在2000ms内，应可以收到40个显示消息，这里暂定30满足即可。
				//offset_pwr_on+= -1*msg.wt;	//使用当前显示值
				flag_zero_pwr_on= 1;
				
				uartS("\n zero@pwr on");
				buz_beep(200);
			}
			else{
				sprintf(str,"\n zero_auto err: cnt=%d< 10 or >zero rage", cnt); 
				uartS(str);
			}
			break;	//跳出循环
		}
		else
			uartS("*");
	}
	
	//dbg_sw&= ~DBG_BUZ;	//注释掉，有按键音（蜂鸣）。
}




/*执行串口标定。
若传入的消息不是MV_MT，什么也不做，等下次。
*/
static void do_ser_cal(MSG_U* msg)
{
	char str[48];
	
	
	if(MSG_TYPE_MV_WT == msg->type){
		if(stab_arr.mv_stab){
			//有数据，且显示稳定，则保持该值。
			//保存旧值。
			CAL_CTX cal_ctx_bk;

			cal_save_to_pt(&cal_ctx_bk);
			
			switch (op_ser_cal.op_sta){
				case 1:	//标定零点
					cal_ctx.pt[0].ad= msg->ad;	
					cal_ctx.pt[0].load= 0;	
					cal_ctx.pt[0].cap_cal= fs_max;										//保存表示是的量程。量程可变。
					cal_ctx.pt[0].div_cal= para_xxx.fd_min;								//保存标定时的分度值，无屌用。
					cal_ctx.pt[0].k= 9.12345678F;		//固定为9.12345678F，看看float表达为什么，输出显示“9.123457”。
					cal_ctx.pt[0].uv= msg->uv;
					
					//（重新）标定零点对加载点的影响。只简单平移ad，不重新计算load。
					//TODO需要知道ad的变化量=new-old= pt[0].ad- pt.ad。
					cal_update_ad(cal_ctx.pt[0].ad- cal_ctx_bk.pt[0].ad);	//更新所有加载点对应的ad值。
				
					break;
				
				case 2:	//标定加载点1	
				case 3:	//标定加载点2
				case 4:	//标定加载点3
				case 5:	//标定加载点4	
					cal_ctx.pt[op_ser_cal.op_sta- 1].uv= msg->uv;	//当前实时上报的uv,ad。
					cal_ctx.pt[op_ser_cal.op_sta- 1].ad= msg->ad;
					cal_ctx.pt[op_ser_cal.op_sta- 1].used= 1;
					cal_ctx.pt[op_ser_cal.op_sta- 1].load= op_ser_cal.op_in;
					cal_ctx.pt[op_ser_cal.op_sta- 1].cap_cal= fs_max;					//保存标定时的量程。量程可变，但此值不变。
					cal_ctx.pt[op_ser_cal.op_sta- 1].div_cal= para_xxx.fd_min;		//标定时的分度值。分度值可变，但此值不变。
					cal_ctx.pt[op_ser_cal.op_sta- 1].div_cnt= (int32_t)(cal_ctx.pt[op_ser_cal.op_sta- 1].load/cal_ctx.pt[op_ser_cal.op_sta- 1].div_cal+ 0.5);	//.div_cnt是一个整数。
				
					cal_ctx.pt[op_ser_cal.op_sta- 1].k= (cal_ctx.pt[op_ser_cal.op_sta- 1].ad-cal_ctx.pt[op_ser_cal.op_sta- 2].ad);
					cal_ctx.pt[op_ser_cal.op_sta- 1].k/=(float)(cal_ctx.pt[op_ser_cal.op_sta- 1].load- cal_ctx.pt[op_ser_cal.op_sta- 2].load);		//!!!必须先强制转换为float，否则值由2601.699951变为2601.000000。
					
					//将本加载点的值拷贝到之后的所有加载点。
					cal_update_other_load(op_ser_cal.op_sta- 1);
						
					break;				

				default:	//超过加载点4时，标定加载点4。				
					break;
			}
			
			
			//
			sprintf(str, "\ndo_ser_cal:point[%d], uv=%d, load=%d, ad=%d", op_ser_cal.op_sta-1, cal_ctx.pt[op_ser_cal.op_sta-1].uv, cal_ctx.pt[op_ser_cal.op_sta-1].load, cal_ctx.pt[op_ser_cal.op_sta-1].ad); 
			uartS(str);
			
			//写入FLASH。
			if(0 == para_to_flash_2(FLASH_UPDATE_MASK_PARA_CAL)){
				uartS("\nok:save ser_cal's point[]");
			
				op_ser_cal.op_sta= 0;	//标定完成：成功。
				op_ser_cal.op_ret= 0;	//失败原因：无。
			}
			else{
				uartS("\nerr:save ser_cal's point[]");
				op_ser_cal.op_sta= -1;	//标定完成：失败。
				op_ser_cal.op_ret= 4;	//失败原因：写FLASH失败。
			}
		}
		else{
			//不稳定，则不执行标定，即标定失败。
			op_ser_cal.op_sta= -1;		//标定完成：失败。
			op_ser_cal.op_ret= 2;		//失败原因：未达到稳定要求。
			
			uartS("\ndo ser cal: NOT STAB");
		}
	}
	else{
		uartS("\n$err:NOT MW_WT");
	}
}



/*限制点n（1~4）是否满足条件。
返回值：0 ---满足，其他 ---不满足。
输入参数 n: 限制点序号（1~4）
输入参数 val: 待比较的值，即称重值。
!!当判断条件（即PX.3有效条件）为9或10时，须使用上一次满足情况。
*/
static int32_t prepoint_satisfy(uint32_t n, int32_t val)
{
	//static int32_t ret_pre= -1;			//上次状态（0--满足，其他--不满足）
	int32_t val1=0,val2=0;				//用于有符号比较。
	int32_t val_min, val_max;			//用于有符号比较。	
	int32_t val_set, val_thred;			//设定值和阈值
	
	uint32_t tmp32;
	uint16_t cond;
	
	
	if(1 == n){
		tmp32= para_xxx.preset_p1_val_1_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p1_val_1_lo;
		val1= (int32_t)tmp32;
		
		tmp32= para_xxx.preset_p1_val_2_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p1_val_2_lo;
		val2= (int32_t)tmp32;
		
		cond= para_xxx.preset_p1_condition;		
	}
	else if(2 == n){
		tmp32= para_xxx.preset_p2_val_1_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p2_val_1_lo;
		val1= (int32_t)tmp32;
		
		tmp32= para_xxx.preset_p2_val_2_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p2_val_2_lo;
		val2= (int32_t)tmp32;
		
		cond= para_xxx.preset_p2_condition;
	}
	else if(3 == n){
		tmp32= para_xxx.preset_p3_val_1_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p3_val_1_lo;
		val1= (int32_t)tmp32;
		
		tmp32= para_xxx.preset_p3_val_2_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p3_val_2_lo;
		val2= (int32_t)tmp32;
		
		cond= para_xxx.preset_p3_condition;
	}
	else if(4 == n){
		tmp32= para_xxx.preset_p4_val_1_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p4_val_1_lo;
		val1= (int32_t)tmp32;
		
		tmp32= para_xxx.preset_p4_val_2_hi;
		tmp32<<= 16;
		tmp32|= para_xxx.preset_p4_val_2_lo;
		val2= (int32_t)tmp32;
		
		cond= para_xxx.preset_p4_condition;
	}
	else{
		cond= 0;	
	}
	
	
	if(0 == cond)
			return -1;	//条件是禁止，直接返回-1（即不满足）。
	
	val_min= val1>val2 ? val2:val1;	//取小者。
	val_max= val1>val2 ? val1:val2;	//取大者。
	
	val_set= val1;
	val_thred= val2;
	
	if(1 == cond){
		//<
		if(val< val_min)
			return 0;	//满足，返回0。
		else
			return -1;	//不满足
	}
	else if(2 == cond){
		//<=
		if(val<= val_min)
			return 0;
		else
			return -1;	//不满足
	}
	else if(3 == cond){
		//==
		if(val == val_min)
			return 0;
		else
			return -1;	//不满足
	}	
	else if(4 == cond){
		//>=
		if(val >= val_min)
			return 0;
		else
			return -1;	//不满足
	}
	else if(5 == cond){
		//>
		if(val > val_min)
			return 0;
		else
			return -1;	//不满足
	}
	else if(6 == cond){
		//!=
		if(val != val_min)
			return 0;
		else
			return -1;	//不满足
	}
	else if(7 == cond){
		//__<>__区间外
		if((val < val_min) || (val> val_max))
			return 0;
		else
			return -1;	//不满足
	}
	else if(8 == cond){
		//=<__>=区间内
		if((val_min<= val) || (val<= val_max))
			return 0;
		else
			return -1;	//不满足
	}
	else if(9 == cond){
		//外部触发：暂未实现，直接返回-1。
		//高限值比较
		if(val>= val_set){
			return 0;	//满足
		}
		else if(val< (val_set-val_thred)){
			return -1;
		}
		else{
			//在[val_set-val_thrd, val_set)时。
			if(preset_point[n].sta_cur){
				return 0;	//上次满足，则本次也满足。
			}
			else 
				return -1;	//上次不满足，本次也不满足。
		}
	}
	else if(10 == cond){
		//低限值比较
		if(val<= val_set){
			return 0;	//满足
		}
		else if(val> (val_set+val_thred)){
			return -1;
		}
		else{
			//在(val_set,val_set+val_thrd]时，若上次满足，则本次满足。
			if(preset_point[n].sta_cur){	
				return 0;	//上次满足，则本次也满足。
			}
			else 
				return -1;	//上次不满足，本次也不满足。
		}
	}
	else
		return -1;		//不满足
}






/*限制点n（1~4）的状态。
输入参数 n:   限制点序号（1~4）
输入参数 val: 称重值
说明：非常繁琐的一个函数。TODO如何化繁为简？
      上次状态，本次状态，都是瞬时状态。
      sta_out输出状态是对瞬时状态处理后的状态，不会瞬时变化。
      sta_fin最终状态是由IN1参与，和“开关量测试开关”参与后的状态。
先更新限制点状态，再进行开关量输出（开关量输出和限制点状态是简单关系）。
转变期：从无效变为有效，或者从有效变为无效。
平稳期：一直有效或一直无效。
*/
static void prepoint_sta(uint32_t n, int32_t val)
{
	uint16_t stab_need=0;		//是否判稳
	uint16_t duration_min=0;	//最小持续时间
	uint16_t cond;				//有效条件
	char str[32];
	
	/*限制点的某些变量，不方便用n索引，搞成变量就摆脱n依赖。
	*/
	if(1 == n){
		stab_need= para_xxx.preset_p1_need_stab;			//是否需要稳定。
		duration_min= para_xxx.preset_p1_duration_min;		//需要稳定时的稳定时间，单位是秒，有1位小数。例123是12.3秒。
		cond= para_xxx.preset_p1_condition;					//判断条件。
	}
	else if(2 == n){
		stab_need= para_xxx.preset_p2_need_stab;
		duration_min= para_xxx.preset_p2_duration_min;
		cond= para_xxx.preset_p2_condition;
	}
	else if(3 == n){
		stab_need= para_xxx.preset_p3_need_stab;
		duration_min= para_xxx.preset_p3_duration_min;
		cond= para_xxx.preset_p3_condition;
	}
	else if(4 == n){
		stab_need= para_xxx.preset_p4_need_stab;
		duration_min= para_xxx.preset_p4_duration_min;
		cond= para_xxx.preset_p4_condition;
	}
	
	
	if(0 == cond)
		return;	//“有效条件”为0，直接退出。
	
	
	/*
	有效条件是高、低限值(9,10)时，不需要稳定时间。条件10是迟滞判断，为国外客户定制的。
	*/
	if((9 == cond) || (10 == cond))	
		stab_need= 0;
	

	
	
	/*是否满足条件
	*/
	if(0 == prepoint_satisfy(n, val)){	//满足条件。
		if((dbg_sw & DBG_PRESET_POINT) && (1==n)){
			sprintf(str, "\n\tsatisfy %d",n);
			uartS(str);
		}
		
		//满足指定条件（返回0），本次状态有效。
		preset_point[n].sta_cur= 1;	
		
		if(preset_point[n].flag_wait_unsatisfy){	//是否等待“变为无效”。只有等待无效，才能向下判断。
			//uartS("\ngoto IN_DEAL");
			goto IN_DEAL;	//跳转到IN对限制值的清零处理。
		}
		
		if(0 == preset_point[n].sta_pre){
			//上次条件状态为无效，本次为有效，则为一个转变，需要记录时间。
			preset_point[n].ts_valid_start= ts_get();	//osKernelGetTickCount();
			preset_point[n].ts_valid_start_valid= 1;
			
			preset_point[n].stab_cnt= 0;
			
			if(dbg_sw & DBG_PRESET_POINT){
				sprintf(str, "\n\tinvalid->valid:%d",n);
				uartS(str);
			}
		}
		else{
			//上次有效，本次有效，没有变化。若起始时间戳无效（即已处理过转变期，现在为平稳期），则不处理。
			if(0 == preset_point[n].ts_valid_start_valid)
				goto IN_DEAL;
		}
		
		
		if(stab_need){	
			/*需要条件判稳。
			若已处在平稳期（0 == preset_point[n].ts_valid_start_valid），则什么都不做。肯定是先转变期，再平稳期。
			*/
			if(preset_point[n].ts_valid_start_valid && (duration_min*100< ts_del_o_n(preset_point[n].ts_valid_start, ts_get()))){	//ms值判断。QQ暂未考虑Tick溢出，因为按ms算，32-bit溢出，需要>49d。
				preset_point[n].sta_out= 1;		//输出状态（已经满足：有效条件和持续时间）。
				preset_point[n].ts_valid_start_valid= 0;	//标示为已处理，处于平稳期。
				
				if(dbg_sw & DBG_PRESET_POINT){
					sprintf(str, "\n\t\t\tsta_out=1(stab):%d", n);
					uartS(str);
				}
			}
		}
		else{
			preset_point[n].sta_out= 1;	//直接的状态
			
			if(dbg_sw & DBG_PRESET_POINT){
				sprintf(str, "\n\t\tsta_out=1(no_stab):%d", n);
				uartS(str);
			}
		}
	}
	else{
		if((dbg_sw & DBG_PRESET_POINT)&& (1==n)){
			sprintf(str, "\nUnsatisfy %d",n);
			uartS(str);
		}
		
		preset_point[n].sta_cur= 0;//不满足指定条件，本次状态无效。
		
		if(1 == preset_point[n].sta_pre){
			//由有效变无效。
			preset_point[n].ts_invalid_start= ts_get();	//osKernelGetTickCount();
			preset_point[n].ts_invalid_start_valid= 1;
			
			preset_point[n].stab_cnt= 0;
			
			if(dbg_sw & DBG_PRESET_POINT){
				sprintf(str, "\n\tvalid->invalid:%d", n);
				uartS(str);
			}
		}
		else{
			//上次无效，本次无效，没有变化。若起始时间戳无效（即已处理过转换期，现在为平稳期），则不处理。
			if(0 == preset_point[n].ts_invalid_start_valid)
				goto DO_UNSATISFY;
		}
		
		
		if(stab_need){
			//需要判稳。
			if(preset_point[n].ts_invalid_start_valid && (duration_min*100< ts_del_o_n(preset_point[n].ts_invalid_start, ts_get()))){		
				preset_point[n].sta_out= 0;	//输出状态
				preset_point[n].ts_invalid_start_valid= 0;		//标示为已处理，处于平稳期。
				
				if(dbg_sw & DBG_PRESET_POINT){
					sprintf(str, "\n\t\t\tsta_out=0(stab):%d", n);
					uartS(str);
				}				
			}
		}
		else{
			preset_point[n].sta_out= 0;	//直接的状态
			
			if(dbg_sw & DBG_PRESET_POINT){
				sprintf(str, "\n\t\tsta_out=0(no_stab):%d", n);
				uartS(str);
			}
		}
		
		
DO_UNSATISFY:	
		if(preset_point[n].flag_wait_unsatisfy){
			if(0 == preset_point[n].sta_out){
				preset_point[n].flag_wait_unsatisfy= 0;	//已检测到状态无效，所以清零等待无效标志。
				
				if(dbg_sw & DBG_PRESET_POINT){
					uartS("\n\t\t\t\t0 -->flag_wait_unsatify");
				}
			}
		}
	}
	

IN_DEAL:	//处理IN。	
	preset_point[n].sta_pre= preset_point[n].sta_cur;	//!!每次比较后，都要更新上次状态。
	
	
	if((9 == cond) || (10 == cond)){	
		/*
		有效条件是高、低限值(9,10)时，不受IN1影响，直接输出当前值。
		*/
		preset_point[n].sta_fin= preset_point[n].sta_out;
	}
	else{
		/*再结合IN有效时，对限制点状态的影响。
		IN1定义为清零限制1~4，且IN1有效。!!IN1只用于一个限制点。当IN1有效时，直接将相应限制点置为无效，设定相应限制点为“等待条件不满足”。		
		*/
		//IN1无效（即不清零，不参与），那么可以用.sta_out更新.sta_fin。
		if(0 == preset_point[n].flag_wait_unsatisfy){
			//不在等待“条件无效”，直接赋值
			preset_point[n].sta_fin= preset_point[n].sta_out;
		}
		else{
			//正在等待“条件重回无效”，值不变。
			preset_point[n].sta_fin= preset_point[n].sta_fin;	//.sta_fin不变。
		}
	}
	
	
	/*再结合“开关量测试开关”，才能进一步更新OUT1，OUT2引脚。否则，不更新OUT1，OUT2。
	*/
	if(para_coil & COIL_BIT_MASK_RST_SW){	//开关量测试开关有效。
		//do nothing.
		uartS("\n sw test io");
	}
	else{	//开关量测试开关无效。
		if(dbg_sw & DBG_PRESET_POINT){
			sprintf(str, "\nsta_fin=%d:%d",preset_point[n].sta_fin, n);
			uartS(str);
		}
		
		//先更新寄存器状态。
		if(1 == n){ 
			if(preset_point[n].sta_fin)
				para_coil|= COIL_BIT_MASK_STA_P1;	//para_01_05.preset_1_sta= 1;
			else
				para_coil&= ~COIL_BIT_MASK_STA_P1;	//para_01_05.preset_1_sta= 0;
		}
		else if(2 == n){ 
			if(preset_point[n].sta_fin)
				para_coil|= COIL_BIT_MASK_STA_P2;	//para_01_05.preset_2_sta= 1;
			else
				para_coil&= ~COIL_BIT_MASK_STA_P2;	//para_01_05.preset_2_sta= 0;
		}
		else if(3 == n){ 
			if(preset_point[n].sta_fin)
				para_coil|= COIL_BIT_MASK_STA_P3;	//para_01_05.preset_3_sta= 1;
			else
				para_coil&= ~COIL_BIT_MASK_STA_P3;	//para_01_05.preset_3_sta= 0;
		}
		else if(4 == n){ 
			if(preset_point[n].sta_fin)
				para_coil|= COIL_BIT_MASK_STA_P4;	//para_01_05.preset_4_sta= 1;
			else
				para_coil&= ~COIL_BIT_MASK_STA_P4;	//para_01_05.preset_4_sta= 0;
		}
		
		/*再更新可能的引脚状态（用preset_point[n].used_n不好）。
		只有在out1_define，out2_define中引用了限制点，才能真正地作用到OUT1，OUT2引脚，否则，只更新寄存器状态。
		*/
		if((2+ n) == para_xxx.out1_define_idx){	//2+n= 3,4,5,6.其中3---限制1，4---限制2，5---限制3，6---限制4。
			//OUT1定义和限制n关联
			if(0 == (para_coil & COIL_BIT_MASK_TEST_SW)){	//不在test io。
				if(preset_point[n].sta_fin)
					OUT1_VALID;
				else
					OUT1_INVALID;
			}
		}
		
		if((2+ n) == para_xxx.out2_define_idx){
			if(preset_point[n].sta_fin)
				OUT2_VALID;
			else
				OUT2_INVALID;
		}		
	}
}



///*预置点n（1~4）的状态。
//输入参数 n: 限制点序号，以指定限制点
//输入参数 val: 称重值
//说明：上次状态，本次状态，都是瞬时状态。
//      sta_out输出状态是对瞬时状态处理后的状态，不会瞬时变化。
//      sta_fin最终状态是由IN1参与，和“开关量测试开关”参与后的状态。
//先更新预置点状态，再进行开关量输出（开关量输出和预置点状态是简单关系）。
//*/
//static void prepoint_sta(uint32_t n, int32_t val)
//{
//	uint16_t stab_need=0;		//是否判稳
//	uint16_t duration_min=0;	//最小持续时间
//	char str[32];
//	
//	/*限制点的某些变量，不方便用n索引，搞成变量就摆脱n依赖。
//	*/
//	if(1 == n){
//		stab_need= para_xxx.preset_p1_need_stab;
//		duration_min= para_xxx.preset_p1_duration_min;
//	}
//	else if(2 == n){
//		stab_need= para_xxx.preset_p2_need_stab;
//		duration_min= para_xxx.preset_p2_duration_min;
//	}
//	else if(3 == n){
//		stab_need= para_xxx.preset_p3_need_stab;
//		duration_min= para_xxx.preset_p3_duration_min;
//	}
//	else if(4 == n){
//		stab_need= para_xxx.preset_p4_need_stab;
//		duration_min= para_xxx.preset_p4_duration_min;
//	}
//	
//	
//	/*是否满足条件
//	*/
//	if(0 == prepoint_satisfy(n, val)){
//		if((dbg_sw & DBG_PRESET_POINT) && (1==n)){
//			sprintf(str, "\n\tsatisfy %d",n);
//			uartS(str);
//		}
//		
//		//满足指定条件（返回0），本次状态有效。
//		preset_point[n].sta_cur= 1;	
//		
//		if(preset_point[n].flag_wait_unsatisfy){	//是否等待“变为无效”。只有等待无效，才能向下判断。
//			uartS("\ngoto IN_DEAL");
//			goto IN_DEAL;	//跳转到IN对限制值的清零处理。
//		}
//		
//		if(0 == preset_point[n].sta_pre){
//			//上次条件状态为无效，本次为有效，则为一个转变，需要记录时间。
//			preset_point[n].ts_valid_start= osKernelGetTickCount();
//			preset_point[n].stab_cnt= 0;
//			
//			if(dbg_sw & DBG_PRESET_POINT){
//				sprintf(str, "\n\tinvalid->valid:%d",n);
//				uartS(str);
//			}
//		}
//		
//			
//		if(stab_need){	
//			if(stab_arr.wt_stab){
//				//AD值判稳定，是由AD处理函数决定的，这里仅使用。
//				preset_point[n].stab_cnt++;
//				
//				if(10< preset_point[n].stab_cnt){
//					preset_point[n].stab_cnt= 11;	//不再自增
//					
//					if((dbg_sw & DBG_PRESET_POINT) && (1== n)){
//						sprintf(str, "\n\tsatisfy's sta_cnt ok:%d", n);
//						uartS(str);
//					}
//				}
//			}
//			
//			//需要条件判稳。
//			if(duration_min*100< (osKernelGetTickCount()- preset_point[n].ts_valid_start)){	//ms值判断。QQ暂未考虑Tick溢出，因为按ms算，32-bit溢出，需要>49d。
//				if(10< preset_point[n].stab_cnt){
//					preset_point[n].sta_out= 1;	//输出状态（既要满足持续时间，又要满足稳定次数）。
//					
//					if(dbg_sw & DBG_PRESET_POINT){
//						sprintf(str, "\n\t\t\tsta_out=1(stab):%d", n);
//						uartS(str);
//					}
//				}
//			}
//		}
//		else{
//			preset_point[n].sta_out= 1;	//直接的状态
//			
//			if(dbg_sw & DBG_PRESET_POINT){
//				sprintf(str, "\n\t\tsta_out=1(dire):%d", n);
//				uartS(str);
//			}
//		}
//	}
//	else{
//		if((dbg_sw & DBG_PRESET_POINT)&& (1==n)){
//			sprintf(str, "\nUnsatisfy %d",n);
//			uartS(str);
//		}
//		
//		preset_point[n].sta_cur= 0;//不满足指定条件，本次状态无效。
//		
//		if(1 == preset_point[n].sta_pre){
//			//由有效变无效。
//			preset_point[n].ts_invalid_start= osKernelGetTickCount();
//			preset_point[n].stab_cnt= 0;
//			
//			if(dbg_sw & DBG_PRESET_POINT){
//				sprintf(str, "\n\tvalid->invalid:%d", n);
//				uartS(str);
//			}
//		}
//		
//		if(stab_need){
//			if(stab_arr.wt_stab){
//				preset_point[n].stab_cnt++;
//				
//				if(10< preset_point[n].stab_cnt){
//					preset_point[n].stab_cnt= 11;	//不再自增
//				
//					if((dbg_sw & DBG_PRESET_POINT) && (1== n)){
//						sprintf(str, "\n\tunsatisfy's sta_cnt ok:%d", n);
//						uartS(str);
//					}					
//				}
//			}
//			
//			//需要判稳。
//			if(duration_min*100< (osKernelGetTickCount()- preset_point[n].ts_invalid_start)){	//ms值判断。QQ暂未考虑Tick
//				
//				if(10< preset_point[n].stab_cnt){
//					preset_point[n].sta_out= 0;	//输出状态
//					
//					if(dbg_sw & DBG_PRESET_POINT){
//						sprintf(str, "\n\t\t\tsta_out=0(stab):%d", n);
//						uartS(str);
//					}
//				}					
//			}
//		}
//		else{
//			preset_point[n].sta_out= 0;	//直接的状态
//			
//			if(dbg_sw & DBG_PRESET_POINT){
//				sprintf(str, "\n\t\tsta_out=0(dire):%d", n);
//				uartS(str);
//			}
//		}
//		
//		
//		if(preset_point[n].flag_wait_unsatisfy){
//			if(0 == preset_point[n].sta_out){
//				preset_point[n].flag_wait_unsatisfy= 0;	//已检测到状态无效，所以清零等待无效标志。
//				
//				uartS("\n\t\t\t\t0 -->flag_wait_unsatify");
//			}
//		}
//	}
//	

//IN_DEAL:	
//	preset_point[n].sta_pre= preset_point[n].sta_cur;
//	
//	/*再结合上IN有效时，对限制点状态的影响。
//	IN1定义为清零限制1~4，且IN1有效。		
//	*/
//	if((1< para_xxx.in1_define)&&(para_xxx.in1_define< 6)){
//		if(in_sta){
//			if((2 == para_xxx.in1_define) && (1 == n)){			//IN1定义为：清零限制点1。		
//				preset_point[n].sta_fin= 0;
//				preset_point[n].flag_wait_unsatisfy= 1;
//				uartS("\nIN1 valid");
//			}
//			else if((3 == para_xxx.in1_define) && (2 == n)){	//IN1定义为：清零限制点2。
//				preset_point[n].sta_fin= 0;
//				preset_point[n].flag_wait_unsatisfy= 1;
//			}
//			else if((4 == para_xxx.in1_define) && (3 == n)){	//IN1定义为：清零限制点3。
//				preset_point[n].sta_fin= 0;
//				preset_point[n].flag_wait_unsatisfy= 1;
//			}
//			else if((5 == para_xxx.in1_define) && (4 == n)){	//IN1定义为：清零限制点4。
//				preset_point[n].sta_fin= 0;
//				preset_point[n].flag_wait_unsatisfy= 1;
//			}
//		}
//		else{
//			//IN1无效，不参与。
//			if(0 == preset_point[n].flag_wait_unsatisfy)
//				preset_point[n].sta_fin= preset_point[n].sta_out;
//		}
//	}
//	else{	
//		//IN1不参与。
//		preset_point[n].sta_fin= preset_point[n].sta_out;	
//	}
//	
//	
//	/*再结合“开关量测试开关”，才能进一步更新OUT1，OUT2引脚。否则，不更新OUT1，OUT2。
//	*/
//	if(para_coil & COIL_BIT_MASK_RST_SW){
//		//do nothing.
//		uartS("\n sw test io");
//	}
//	else{
//		if(dbg_sw & DBG_PRESET_POINT){
//			sprintf(str, "\nsta_fin=%d:%d",preset_point[n].sta_fin, n);
//			uartS(str);
//		}
//		//先更新寄存器状态
//		if(1 == n){ 
//			if(preset_point[n].sta_fin)
//				para_01_05.preset_1_sta= 1;
//			else
//				para_01_05.preset_1_sta= 0;
//		}
//		else if(2 == n){ 
//			if(preset_point[n].sta_fin)
//				para_01_05.preset_2_sta= 1;
//			else
//				para_01_05.preset_2_sta= 0;
//		}
//		else if(3 == n){ 
//			if(preset_point[n].sta_fin)
//				para_01_05.preset_3_sta= 1;
//			else
//				para_01_05.preset_3_sta= 0;
//		}
//		else if(4 == n){ 
//			if(preset_point[n].sta_fin)
//				para_01_05.preset_4_sta= 1;
//			else
//				para_01_05.preset_4_sta= 0;
//		}
//		
//		/*再更新可能的引脚状态（用preset_point[n].used_n不好）。
//		只有在out1_define，out2_define中引用了限制点，才能正真的作用到OUT1，OUT2引脚，否则，只更改限制点状态。
//		*/
//		if((2+ n) == para_xxx.out1_define){
////			if(dbg_sw & DBG_PRESET_POINT){
////				uartS("\n out1_define");
////			}
//			//OUT1定义和限制n关联
//			if(preset_point[n].sta_fin)
//				OUT1_VALID;
//			else
//				OUT1_INVALID;
//		}
//		
//		if((2+ n) == para_xxx.out2_define){
////			if(dbg_sw & DBG_PRESET_POINT){
////				uartS("\n out2_define");
////			}
//			if(preset_point[n].sta_fin)
//				OUT2_VALID;
//			else
//				OUT2_INVALID;
//		}		
//	}
//}

static void spoint_config(void)
{		
	preset_point[1].sta_pre= 0;					//不满足
	preset_point[1].sta_cur= 0;					//不满足
	preset_point[1].flag_wait_unsatisfy= 0;		//不是”等待变为不满足”。
	preset_point[1].ts_valid_start_valid= 0;	//时间戳无效
	preset_point[1].ts_invalid_start_valid= 0;	//时间戳无效

	preset_point[2].sta_pre= 0;
	preset_point[2].sta_cur= 0;
	preset_point[2].flag_wait_unsatisfy= 0;
	preset_point[2].ts_valid_start_valid= 0;
	preset_point[2].ts_invalid_start_valid= 0;

	preset_point[3].sta_pre= 0;
	preset_point[3].sta_cur= 0;
	preset_point[3].flag_wait_unsatisfy= 0;
	preset_point[3].ts_valid_start_valid= 0;
	preset_point[3].ts_invalid_start_valid= 0;

	preset_point[4].sta_pre= 0;
	preset_point[4].sta_cur= 0;
	preset_point[4].flag_wait_unsatisfy= 0;
	preset_point[4].ts_valid_start_valid= 0;
	preset_point[4].ts_invalid_start_valid= 0;
}	




/************************ END OF FILE ************************/
